﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Framework;
using Umbraco.Framework.Context;
using Umbraco.Framework.DataManagement.Linq.QueryModel;
using Umbraco.Framework.DataManagement.Linq.ResultBinding;
using Umbraco.Framework.Persistence;
using Umbraco.Framework.Persistence.Model;
using Umbraco.Framework.Persistence.Model.Associations;
using Umbraco.Framework.Persistence.Model.Associations._Revised;
using Umbraco.Framework.Persistence.Model.Attribution;
using Umbraco.Framework.Persistence.Model.Constants;
using Umbraco.Framework.Persistence.Model.Constants.AttributeDefinitions;
using Umbraco.Framework.Persistence.ProviderSupport._Revised;
using Umbraco.Hive.ProviderSupport;
using WebGarden.Hive.Providers.Products.Entity.Model;
using WebGarden.Hive.Providers.Products.Schema.Model;
using WebGarden.Hive.Providers.Products.Schema.Model.AttributeDefinitions;

namespace WebGarden.Hive.Providers.Products.Entity
{
	/// <summary>
	/// This class performs the actual data retrieval for our provider
	/// </summary>
	public class Repository : AbstractEntityRepository
	{
		private dynamic _productLoader;

		public Repository(ProviderMetadata providerMetadata, AbstractSchemaRepository schemas, IFrameworkContext frameworkContext)
			: base(providerMetadata, schemas, frameworkContext)
		{ }

		#region Properties

		/// <summary>
		/// Gets a value indicating whether this instance can read relations.
		/// </summary>
		/// <value>
		/// <c>true</c> if this instance can read relations; otherwise, <c>false</c>.
		/// </value>
		public override bool CanReadRelations { get { return true; } }

		/// <summary>
		/// Gets a value indicating whether this instance can write relations.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance can write relations; otherwise, <c>false</c>.
		/// </value>
		public override bool CanWriteRelations { get { return false; } }

		/// <summary>
		/// Gets the product loader.
		/// </summary>
		private dynamic ProductLoader { get { return _productLoader ?? (_productLoader = new Product()); } }

		#endregion

		#region Entity

		/// <summary>
		/// Identifies if a <see cref="!:TEntity"/> with matching <paramref name="id"/> can be found in this repository.
		/// </summary>
		/// <typeparam name="TEntity">The type of the entity.</typeparam><param name="id">The id.</param>
		/// <returns>
		/// <code>
		/// true
		/// </code>
		///  if the item with <paramref name="id"/> can be found, otherwise 
		/// <code>
		/// false
		/// </code>
		/// .
		/// </returns>
		public override bool Exists<TEntity>(HiveId id)
		{
			return Get<TEntity>(true, id).Any();
		}

		/// <summary>
		/// Performs the add or update. This is a read-only provider so this does nothing.
		/// </summary>
		/// <param name="entity">The entity.</param>
		protected override void PerformAddOrUpdate(TypedEntity entity)
		{
			return;
		}

		/// <summary>
		/// Performs the delete. This is a read-only provider so this does nothing.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="id">The id.</param>
		protected override void PerformDelete<T>(HiveId id)
		{
			return;
		}

		/// <summary>
		/// Performs the get. This is where we actually retrieve the data from our products table.
		/// See the Wordpress provider for a more complex scenario here.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="allOrNothing">if set to <c>true</c> [all or nothing].</param>
		/// <param name="ids">The ids.</param>
		/// <returns></returns>
		protected override IEnumerable<T> PerformGet<T>(bool allOrNothing, params HiveId[] ids)
		{
			// Products
			foreach (var hiveId in ids.Where(x => x.Value.Type == HiveIdValueTypes.Int32).Select(x => Convert.ToInt32(x.Value.ToString())))
			{
				yield return ConvertProductToTypedEntity(ProductLoader.Single(hiveId)) as T;
			}
		}

		/// <summary>
		/// Performs the get all.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		protected override IEnumerable<T> PerformGetAll<T>()
		{
			// Products
			dynamic productLoader = new Product();
			var products = productLoader.All();
			foreach (var product in products)
			{
				yield return ConvertProductToTypedEntity(product);
			}
		}

		// ----------------------------------------------------------- //

		/// <summary>
		/// Performs the execute many.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="query">The query.</param>
		/// <param name="objectBinder">The object binder.</param>
		/// <returns></returns>
		public override IEnumerable<T> PerformExecuteMany<T>(QueryDescription query, ObjectBinder objectBinder)
		{
			return Enumerable.Empty<T>();
		}

		/// <summary>
		/// Performs the execute scalar.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="query">The query.</param>
		/// <param name="objectBinder">The object binder.</param>
		/// <returns></returns>
		public override T PerformExecuteScalar<T>(QueryDescription query, ObjectBinder objectBinder)
		{
			return default(T);
		}

		/// <summary>
		/// Performs the execute single.
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <param name="query">The query.</param>
		/// <param name="objectBinder">The object binder.</param>
		/// <returns></returns>
		public override T PerformExecuteSingle<T>(QueryDescription query, ObjectBinder objectBinder)
		{
			return default(T);
		}

		#endregion

		#region Relations

		/// There are no relations defined in our simple products database so the relations methods throw NotImplementedExceptions

		/// <summary>
		/// Adds the relation.
		/// </summary>
		/// <param name="item">The item.</param>
		public override void AddRelation(IReadonlyRelation<IRelatableEntity, IRelatableEntity> item)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Removes the relation.
		/// </summary>
		/// <param name="item">The item.</param>
		public override void RemoveRelation(IRelationById item)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Performs the get parent relations.
		/// </summary>
		/// <param name="childId">The child id.</param>
		/// <param name="relationType">Type of the relation.</param>
		/// <returns></returns>
		public override IEnumerable<IRelationById> PerformGetParentRelations(HiveId childId, RelationType relationType = null)
		{
			throw new NotImplementedException();
		}

		/// <summary>
        /// Performs the get child relations.
        /// </summary>
        /// <param name="parentId">The parent id.</param>
        /// <param name="relationType">Type of the relation.</param>
        /// <returns></returns>
        public override IEnumerable<IRelationById> PerformGetChildRelations(HiveId parentId, RelationType relationType = null)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Performs the get ancestor relations.
		/// </summary>
		/// <param name="descendentId">The descendent id.</param>
		/// <param name="relationType">Type of the relation.</param>
		/// <returns></returns>
		public override IEnumerable<IRelationById> PerformGetAncestorRelations(HiveId descendentId, RelationType relationType = null)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Performs the get descendent relations.
		/// </summary>
		/// <param name="ancestorId">The ancestor id.</param>
		/// <param name="relationType">Type of the relation.</param>
		/// <returns></returns>
		public override IEnumerable<IRelationById> PerformGetDescendentRelations(HiveId ancestorId, RelationType relationType = null)
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Performs the find relation.
		/// </summary>
		/// <param name="sourceId">The source id.</param>
		/// <param name="destinationId">The destination id.</param>
		/// <param name="relationType">Type of the relation.</param>
		/// <returns></returns>
		public override IRelationById PerformFindRelation(HiveId sourceId, HiveId destinationId, RelationType relationType)
		{
			throw new NotImplementedException();
		}

		#endregion

		#region Helper Methods

		/// <summary>
		/// Gets the product as a typed entity.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns></returns>
		private static TypedEntity ConvertProductToTypedEntity(dynamic item)
		{
			if (item == null)
				return null;

			// Create a typed entity with a unique id, and set the schema to be that for our product
			var output = new TypedEntity()
			{
				Id = new HiveId("Product-" + item.id),
				EntitySchema = new ProductSchema()
			};

			// Products have two attributes: description and price - both need to be created for our typed entity
			// These are defined in the Schema/AttributeDefinitions folder
			var descriptionAttribute = new TypedAttribute(new DescriptionAttributeDefinition(), item.description);
			descriptionAttribute.Id = new HiveId("Description");
			output.Attributes.Add(descriptionAttribute);

			var priceAttribute = new TypedAttribute(new PriceAttributeDefinition(), item.price);
			priceAttribute.Id = new HiveId("Price");
			output.Attributes.Add(priceAttribute);

			return output;
		}

		#endregion

		/// <summary>
		/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
		/// </summary>
		protected override void DisposeResources()
		{
			return;
		}
	}
}
